package org.cloud.dimension.utils;


import brave.ScopedSpan;
import brave.Tracer;
import cn.hutool.core.util.IdUtil;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cloud.core.redis.RedisUtil;
import org.cloud.entity.LoginUserDetails;
import org.cloud.exception.BusinessException;
import org.cloud.feign.service.ICommonServiceFeignClient;
import org.cloud.utils.EnvUtil;
import org.cloud.utils.HttpServletUtil;
import org.cloud.utils.IPUtil;
import org.cloud.utils.SpringContextUtil;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;

@Slf4j
public final class LoginUtil {

    private final RestTemplate restTemplate;
    private final ICommonServiceFeignClient commonServiceFeignClient;
    private final Tracer tracer;

    private LoginUtil() {
        restTemplate = SpringContextUtil.getBean(RestTemplate.class);
        commonServiceFeignClient = SpringContextUtil.getBean(ICommonServiceFeignClient.class);
        tracer = SpringContextUtil.getBean(Tracer.class);
    }

    private static final LoginUtil instance = new LoginUtil();

    public static LoginUtil single() {
        return instance;
    }

    public Map<String, Object> login(MultiValueMap<String, String> params, Map<String, String> headerMap) {
        final String applicationGroup = EnvUtil.single().getEnv("spring.application.group", "");
        String url = "http://" + applicationGroup + "SPRING-GATEWAY/auth/login";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //  请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
        HttpServletRequest request = HttpServletUtil.single().getHttpServlet();
        if (request != null) {
            headers.set("X-Forwarded-For", IPUtil.single().getIpAddress(request));
        }
        if (!ObjectUtils.isEmpty(headerMap)) {
            headerMap.forEach(headers::add);
        }
        //  封装参数，千万不要替换为Map与HashMap，否则参数无法传递
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers);
        for (int i = 0; i < 5; i++) {  // 暂时增加5次重试，防止有时候报错
            try {
                ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Map.class);//  执行HTTP请求
                return response.getBody();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    public Map<String, Object> login(MultiValueMap<String, String> params) {
        return login(params, null);
    }

    /**
     * 根据token获取用户
     *
     * @param token
     * @return
     */
    public LoginUserDetails getUserByToken(String token) {
        RestTemplate restTemplate = SpringContextUtil.getBean(RestTemplate.class);
        final String applicationGroup = EnvUtil.single().getEnv("spring.application.group", "");
        String url = "http://" + applicationGroup + "SPRING-GATEWAY/user/info/authentication";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "basic " + token);
        MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(param,
            headers); //  封装参数，千万不要替换为Map与HashMap，否则参数无法传递
        for (int i = 0; i < 5; i++) {  // 暂时增加5次重试，防止有时候报错
            try {
                ResponseEntity<LoginUserDetails> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity,
                    LoginUserDetails.class);//  执行HTTP请求
                return response.getBody();
            } catch (Exception e) {
                log.error("{}", e.getMessage());
            }
        }
        return null;
    }

    public void logoutFrameUser(String frameUserName) {
        LoginUserDetails loginUserDetails = this.getFrameUser(frameUserName);
        if (loginUserDetails == null) {
            return;
        }
        RedisUtil.single().removeUserLoginToken(loginUserDetails.getId());
    }

    public LoginUserDetails getFrameUser(String frameUserName) {
        ScopedSpan span = tracer.startScopedSpan(IdUtil.fastSimpleUUID());
        try {
            return commonServiceFeignClient.getUserByUserName(frameUserName);
        } finally {
            span.finish();
        }
    }

    private final String USER_LOGIN_FAIL_COUNT_KEY = "system:USER_LOGIN_FAIL_COUNT:%s"; // 登录失败次数
    private final String USER_LOGIN_LAST_LOGIN_TIME_KEY = "system:USER_LOGIN_LAST_LOGIN_TIME:%s"; // 上次尝试登录时间
    // 用户登录失败次数超过5次，锁定key值
    private final String USER_LOGIN_FAIL_COUNT_LOCK_KEY = "system:USER_LOGIN_FAIL_COUNT_LOCK:%s";

    // 增加用户登录失败次数
    public void addUserLoginFailCount(String userName) {
        RedisUtil.getRedisTemplate().opsForValue().increment(String.format(USER_LOGIN_FAIL_COUNT_KEY, userName));
    }

    // 清空登录失败次数
    public void clearUserLoginFailCount(String userName) {
        RedisUtil.single().remove(String.format(USER_LOGIN_FAIL_COUNT_KEY, userName));
    }

    // 登录失败次数超过5次，锁定1小时
    public void isUserLoginFailCountOverLimit(String userName) throws BusinessException {
        isUserLoginFailCountOverLimit(userName, 3600L, 5);
    }

    public void isUserLoginFailCountOverLimit(String userName, Long lockTime, Integer maxFailCount) throws BusinessException {
        if (RedisUtil.single().exists(String.format(USER_LOGIN_FAIL_COUNT_LOCK_KEY, userName))) {
            throw new BusinessException(LoginErrorCode.LOGIN_FAIL_COUNT_LOCK.i18n);
        }
        Integer failCount = RedisUtil.single().get(String.format(USER_LOGIN_FAIL_COUNT_KEY, userName));
        if (failCount == null || failCount < maxFailCount) {
            return;
        }
        RedisUtil.single().set(String.format(USER_LOGIN_FAIL_COUNT_LOCK_KEY, userName), "locked", lockTime);
        clearUserLoginFailCount(userName);
        throw new BusinessException(LoginErrorCode.LOGIN_FAIL_COUNT_LOCK.i18n);
    }

    // 解锁用户
    public void unlockUser(String userName) {
        clearUserLoginFailCount(userName);
        RedisUtil.single().remove(String.format(USER_LOGIN_FAIL_COUNT_LOCK_KEY, userName));
    }

    // 设置上次登录时间
    public void setUserLastLoginTime(String userName) {
        RedisUtil.getRedisTemplate().opsForValue().set(String.format(USER_LOGIN_LAST_LOGIN_TIME_KEY, userName), System.currentTimeMillis());
    }

    // 距离上次登录时间15秒以内不如许再次登录
    public void isUserLastLoginTime(String userName) {
        isUserLastLoginTime(userName, 15L);
    }

    public void isUserLastLoginTime(String userName, Long seconds) {
        Long lastLoginTime = RedisUtil.single().get(String.format(USER_LOGIN_LAST_LOGIN_TIME_KEY, userName));
        Assert.isTrue(lastLoginTime == null || System.currentTimeMillis() - lastLoginTime > seconds * 1000,
            LoginErrorCode.LOGIN_LAST_LOGIN_TIME_LOCK.i18n);
    }

    @AllArgsConstructor
    enum LoginErrorCode {
        // 失败次数过多被锁定
        LOGIN_FAIL_COUNT_LOCK("login.fail.count.lock", "登录失败次数过多，请1小时后再试"),
        // 距离上次登录时间过短，不允许再次登录
        LOGIN_LAST_LOGIN_TIME_LOCK("login.last.login.time.lock", "距离上次登录时间过短，不允许再次登录"),
        ;
        public final String i18n;
        public final String message;


    }


}
